package edu.cuit.canary.core.service.bean;

import edu.cuit.canary.core.mapper.BookMapper;
import edu.cuit.canary.core.service.BookService;
import edu.cuit.canary.entity.po.Book;
import jakarta.annotation.Resource;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service @Transactional
public class BookServiceBean implements BookService {
    @Resource private BookMapper mapper;

    /**
     * 图书保存成功后(数据成功添加进数据库)，将图书信息添加进redis
     * @CachePut: 将方法返回值缓存进redis
     */
    @CachePut(value = "cache", key = "'book:' + #result.id")
    @Override
    public Book save(Book book) {
        mapper.insert(book); //保存成功后进行主键回填
        return book;
    }

    /**
     * 数据删除成功,同步删除redis中的缓存数据
     */
    @CacheEvict(value = "cache", key = "'book:' + id")
    @Override
    public void removeById(Integer id) {
        mapper.deleteById(id);
    }

    /**
     * 1. 进入方法前读取redis中的数据
     *    1.1 缓存命中=====>直接返回缓存中的数据,不执行方法
     *    1.2 缓存未命中===>执行方法，查找数据库，同时将查找到的数据添加进缓存
     *
     * 缓存穿透: 恶意用户大量访问缓存和数据库都不存在的数据
     *     解决方案:
     *     1.在缓存中缓存null值,同时设置一定的过期时间
     *     2.布隆过滤器
     * 缓存击穿: 一个热点key(高并发)突然过期(失效),无数请求会直接达到数据库
     *     1.热点key永不过期
     *     2.缓存重建时加互斥锁
     *
     * 缓存雪崩: 大量key在同一时间集中失效;缓存服务器宕机
     *     1. 缓存预热时，给不同的key给定不同的过期时间
     *     2. 集群缓存提升可用性
     */
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    @Override
    @Cacheable(value = "cache", key = "'book:' + #id", sync = true)
    public Book findById(Integer id) {
        return mapper.selectById(id);
    }

    /**
     * 数据更新成功，同步更新缓存======>双写一致性
     * 使用延迟双删
     * @param book
     */
    @CacheEvict(value = "cache", key = "'book:' + #book.id") //业务执行成功，删除缓存
    @Override
    public void update(Book book) {

        mapper.updateById(book);
        //防止其它线程在业务过程中再次从数据库中读取了修改前的脏数据,所以需要在业务完成后再次删除缓存=>由读取数据再次完成缓存重建
    }
}
